#include "Math/Vector2D.h"

using namespace BR;

/*
 *  2D vector
 */

/*
 *  static member constant
 */
template<class Tp>
const Vector2D<Tp> Vector2D<Tp>::ZERO = Vector2D<Tp>();

/*
 *  constructor
 */
template<class Tp>
Vector2D<Tp>::Vector2D(const Tp& I, const Tp& J) : i(I), j(J) { }

template<class Tp>
Vector2D<Tp>::Vector2D(const Vector2D<Tp>& other) : i(other.i), j(other.j) { }

/*
 *  operator
 */
template<class Tp>
Vector2D<Tp>& Vector2D<Tp>::operator=(const Vector2D<Tp>& rhs) { i = rhs.i; j = rhs.j; return *this; }

template<class Tp>
Vector2D<Tp> Vector2D<Tp>::operator+(const Vector2D<Tp>& rhs) const { return Vector2D<Tp>(i+rhs.i, j+rhs.j); }

template<class Tp>
Vector2D<Tp> Vector2D<Tp>::operator-(const Vector2D<Tp>& rhs) const { return Vector2D<Tp>(i-rhs.i, j-rhs.j); }

template<class Tp>
Vector2D<Tp> Vector2D<Tp>::operator*(const Tp& rhs) const { return Vector2D<Tp>(i*rhs, j*rhs); }

template<class Tp>
Vector2D<Tp> Vector2D<Tp>::operator/(const Tp& rhs) const { return Vector2D<Tp>(i/rhs, j/rhs); }

template<class Tp>
Vector2D<Tp>& Vector2D<Tp>::operator+=(const Vector2D<Tp>& rhs) { i += rhs.i; j += rhs.j; return *this; }

template<class Tp>
Vector2D<Tp>& Vector2D<Tp>::operator-=(const Vector2D<Tp>& rhs) { i -= rhs.i; j -= rhs.j; return *this; }

template<class Tp>
Vector2D<Tp>& Vector2D<Tp>::operator*=(const Tp& rhs) { i *= rhs; j *= rhs; return *this; }

template<class Tp>
Vector2D<Tp>& Vector2D<Tp>::operator/=(const Tp& rhs) { i /= rhs; j /= rhs; return *this; }

template<class Tp>
Vector2D<Tp> Vector2D<Tp>::operator+() const { return Vector2D<Tp>(i, j); }

template<class Tp>
Vector2D<Tp> Vector2D<Tp>::operator-() const { return Vector2D<Tp>(-i, -j); }

template<class Tp>
const Tp& Vector2D<Tp>::operator[](size_t idx) const
{
	switch(idx) {
		case 0: return i;
		case 1: return j;
		default: throw std::out_of_range();
	}
}

template<class Tp>
Tp& Vector2D<Tp>::operator[](size_t idx)
{
	switch(idx) {
		case 0: return i;
		case 1: return j;
		default: throw std::out_of_range();
	}
}

/*
 *  member function
 */
template<class Tp>
constexpr Tp Vector2D<Tp>::Dot(const Vector2D<Tp>& rhs) const { return i*rhs.i + j*rhs.j; }

template<class Tp>
constexpr Tp Vector2D<Tp>::Cross(const Vector2D<Tp>& rhs) const { return i*rhs.j - j*rhs.i; }

template<class Tp>
constexpr Tp Vector2D<Tp>::Norm() const { return sqrt(i*i + j*j); }

template<class Tp>
constexpr Tp Vector2D<Tp>::NormSqr() const { return i*i + j*j; }

template<class Tp>
Vector2D<Tp>& Vector2D<Tp>::Unitize() { Tp len = sqrt(i*i + j*j); i /= len; j /= len; return *this; }

template<class Tp>
Vector2D<Tp> Vector2D<Tp>::Unit() const { Tp len = sqrt(i*i + j*j); return Vector2D<Tp>(i/len, j/len); }

template<class Tp>
constexpr Tp Vector2D<Tp>::Arg() const { return atan2(j, i); }

template<class Tp>
constexpr Tp Vector2D<Tp>::TanArg() const { return j / i; }

template<class Tp>
Vector2D<Tp>& Vector2D<Tp>::Rotate(const Tp& Angle)
{
	Tp Sin = sin(Angle), Cos = cos(Angle); Tp ti = i * Cos - j * Sin, tj = i * Sin + j * Cos; i = ti; j = tj;
	return *this;
}

template<class Tp>
Vector2D<Tp>& Vector2D<Tp>::Rotate(const Tp& Sin, const Tp& Cos)
{
	Tp ti = i * Cos - j * Sin, tj = i * Sin + j * Cos; i = ti; j = tj;
	return *this;
}

/*
 *  hepler function
 */
template<class Tp>
Vector2D<Tp> Rotate(const Vector2D<Tp>& lhs, const Tp& Angle)
{
	Tp Sin = sin(Angle), Cos = cos(Angle);
	return Vector2D<Tp>(lhs.i*Cos - lhs.j*Sin, lhs.i*Sin + lhs.j*Cos);
}

template<class Tp>
constexpr Vector2D<Tp> Rotate(const Vector2D<Tp>& lhs, const Tp& Sin, const Tp& Cos)
{
	return Vector2D<Tp>(lhs.i*Cos - lhs.j*Sin, lhs.i*Sin + lhs.j*Cos);
}

template<class Tp>
constexpr Vector2D<Tp> operator*(const Tp& lhs, const Vector2D<Tp>& rhs)
{
	return Vector2D<Tp>(lhs*rhs.i, lhs*rhs.j);
}

template<class Tp>
constexpr Tp Angle(const Vector2D<Tp>& lhs, const Vector2D<Tp>& rhs)
{
	return acos( (lhs.i*rhs.i + lhs.j*rhs.j) / sqrt( (lhs.i*lhs.i + lhs.j*lhs.j) * (rhs.i*rhs.i + rhs.j*rhs.j) ) );
}

template<class Tp>
constexpr Tp CosAngle(const Vector2D<Tp>& lhs, const Vector2D<Tp>& rhs)
{
	return (lhs.i*rhs.i + lhs.j*rhs.j) / sqrt( (lhs.i*lhs.i + lhs.j*lhs.j) * (rhs.i*rhs.i + rhs.j*rhs.j) );
}
